#!/usr/bin/env python
#-*- encoding:utf-8 -*-

import sys
import os
import mimetypes
import wx
import json
import struct
import base64
import array
import shutil
import traceback

APP_NAME='imagetext'
IMAGETEXT_ICO = APP_NAME + '.ico'
FILE_VERSION=1
AUTH_URL='https://imagetext.sevenever.com/auth/%s'

class Dick(dict):
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError, k:
            raise AttributeError, k
    
    def __setattr__(self, key, value):
        self[key] = value
    
    def __delattr__(self, key):
        try:
            del self[key]
        except KeyError, k:
            raise AttributeError, k
    
    def __repr__(self):     
        return '<Storage ' + dict.__repr__(self) + '>'

class Settings(Dick):
    default = {'pointSize': 48,
               'family': wx.FONTFAMILY_DEFAULT,
               'style': wx.FONTSTYLE_NORMAL,
               'weight': wx.FONTWEIGHT_NORMAL,
               'underline': False,
               'faceName': 'Tahoma',
               'red': 0,
               'green': 0,
               'blue': 0,
               'alpha': 50,
               'addwm': True,
               'logoFile': 'logo.jpg',
               'logoWidth': 20,
               'logoHeight': 20,
               'samelogoWH': True,
               'logoX': 10,
               'logoY': 10,
               'opacity': 64,
               'txtX': 10,
               'txtY': 80,
               'chgfnbytxt': True,
               }
    def __init__(self, filename):
        self.filename = filename
        if not os.path.exists(filename):
            self.save()
        else:
            s = json.loads(open(filename).read())
            for key in s:
                self[key] = s[key]
    
    def save(self):
        self.saveto(self.filename)
    
    def saveto(self, filename):
        open(filename, 'wb').write(json.dumps(self))
    
    def __getattr__(self, key):
        try:
            if self.has_key(key):
                return self[key]
            self[key] = Settings.default[key]
            return self[key]
        except KeyError, k:
            raise AttributeError, k

class ResourceManager(object):
    def __init__(self):
        '''
        If we run it from source, we have resource directory under the source direcory.
        On Windows, We also always have resource directory under the source/executablebin directory.
        On Linux, we have resource directory under /usr/share/lib/imagetext.
        On MAC, I dont know ...'''
        self.app_dir = self.get_app_dir()
        self.context = (self.platform(), self.setup())
        if self.context == ('mac', 'frozen'):
            self.resc_dir = os.path.join(self.app_dir, 'Contents','Resources') # py2app
        elif self.context == ('linux', 'package'):
            self.resc_dir = os.path.join(sys.prefix, 'share', APP_NAME) # linux
        else: # windows or from source
            self.resc_dir = os.path.join(self.app_dir, 'resource')
        
    def get(self, res):
        res = res.lstrip(os.path.sep)
        return os.path.join(self.resc_dir, res)
    
    @staticmethod
    def is_package():
        return not sys.argv[0].endswith('.py')
    
    @staticmethod
    def get_app_dir():
        if ResourceManager.setup() != 'source':
            return os.path.abspath(os.path.dirname(sys.executable))
        return os.path.abspath(os.path.dirname(sys.argv[0]))
    
    @staticmethod
    def setup():
        if hasattr(sys, 'frozen'):
            return 'frozen'
        elif ResourceManager.is_package():
            return 'package'
        return 'source'
    
    @staticmethod
    def platform():
        if sys.platform.startswith('win'):
            return 'windows'
        elif sys.platform.startswith('darwin'):
            return 'mac'
        return 'linux'

class MainFrame(wx.Frame):
    """Frame class that displays an image."""
    def __init__(self,
                 settings,
                 resourcemanager,
                 parent=None, id=-1,
                 pos=wx.DefaultPosition,
                 title='Image Text'):
        self.setting = settings
        self.LoadSettings()
        self.rm = resourcemanager
        """Create a Frame instance and display image."""
        wx.Frame.__init__(self, parent, id, title, pos, None, wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.MAXIMIZE_BOX |wx.SYSTEM_MENU|wx.CAPTION|wx.RESIZE_BORDER)
        self.panel = wx.Panel(self)
        self.imgpanel = wx.Panel(self.panel, -1)
        self.txtpanel = wx.Panel(self.panel, -1)
        self.AddMenus()
        self.AddControls()
        
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.imgpanel, 1, flag=wx.EXPAND)
        sizer.Add(self.txtpanel, 0, flag=wx.EXPAND)
        self.panel.SetSizer(sizer)
        self.panel.Fit()
        self.panel.SetAutoLayout(1)
        self.Show()
        self.Maximize()
        self.version = FILE_VERSION
        self.taskfn = None
        self.FileList = []
        self.Index = 0
        self.image = None
        self.InitImageBuffer()
        self.imgpanel.Bind(wx.EVT_SIZE, self.OnImageSize)
        self.imgpanel.Bind(wx.EVT_PAINT, self.PaintImage)
        
        self.colourDlg = wx.ColourDialog(None)
        
        icon=wx.EmptyIcon()
        icon.LoadFile(self.rm.get(IMAGETEXT_ICO), wx.BITMAP_TYPE_ICO)
        self.SetIcon(icon)
        
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.previewed = False
        self.dirty = False
    def OnImageSize(self, event):
        self.InitImageBuffer()
    def PaintImage(self, event):
        wx.BufferedPaintDC(self.imgpanel, self.imageBuffer)
    def InitImageBuffer(self):
        w, h = self.imgpanel.GetClientSize()
        self.imageBuffer = wx.EmptyBitmap(w, h)
        self.LoadImage()
    
    def OnClose(self, event):
        if self.dirty:
            if not self.taskfn:
                if wx.MessageBox('Some work has to be saved, save them now?', caption='Save work', style=wx.YES|wx.NO) == wx.YES:
                    self.SaveTask(None)
                    if self.dirty and event.CanVeto():
                        event.Veto()
                        return
            else:
                self.SaveTaskToFile(self.taskfn)
        self.Destroy()
    
    def LoadSettings(self):
        self.colour = wx.Colour(self.setting.red, self.setting.green, self.setting.blue, self.setting.alpha)
        self.font = wx.Font(self.setting.pointSize, self.setting.family, self.setting.style, self.setting.weight, self.setting.underline, self.setting.faceName)
        self.setting.logoWidth = self.restrict(self.setting.logoWidth, 0, 100)
        self.setting.logoHeight = self.restrict(self.setting.logoHeight, 0, 100)
        self.setting.logoX = self.restrict(self.setting.logoX, 0, 100)
        self.setting.logoY = self.restrict(self.setting.logoY, 0, 100)
        self.setting.opacity = self.restrict(self.setting.opacity, 0, 255)
        self.setting.txtX = self.restrict(self.setting.txtX, 0, 100)
        self.setting.txtY = self.restrict(self.setting.txtY, 0, 100)
        self.setting.save()
    def restrict(self, value, l, h):
        if value < l:
            return l
        if value > h:
            return h
        return value
    
    def AddControls(self):
        # text Control
        self.txt = wx.TextCtrl(self.txtpanel, -1, style=wx.TE_MULTILINE|wx.TE_RICH2)
        self.txt.SetPosition((10, 50))
        f = self.txt.GetFont()
        f.SetPointSize(48)
        self.txt.SetFont(f)
        # first button
        self.first = wx.Button(self.txtpanel, -1, "<<")
        self.first.Bind(wx.EVT_BUTTON, self.GoFirst)
        # pre button
        self.pre = wx.Button(self.txtpanel, -1, "<")
        self.pre.Bind(wx.EVT_BUTTON, self.GoPre)
        # done button
        self.done = wx.Button(self.txtpanel, -1, "+")
        self.done.Bind(wx.EVT_BUTTON, self.Preview)
        # discard button
        self.discard = wx.Button(self.txtpanel, -1, "-")
        self.discard.Bind(wx.EVT_BUTTON, self.Discard)
        # next button
        self.next = wx.Button(self.txtpanel, -1, ">")
        self.next.Bind(wx.EVT_BUTTON, self.GoNext)
        # last button
        self.last = wx.Button(self.txtpanel, -1, ">>")
        self.last.Bind(wx.EVT_BUTTON, self.GoLast)
        # newtask button
        self.newtaskBtn = wx.Button(self.txtpanel, -1, "New Task")
        self.newtaskBtn.Bind(wx.EVT_BUTTON, self.NewTask)
        # opentask button
        self.opentaskBtn = wx.Button(self.txtpanel, -1, "Open Task")
        self.opentaskBtn.Bind(wx.EVT_BUTTON, self.OpenTask)
        # savetask button
        self.savetaskBtn = wx.Button(self.txtpanel, -1, "Save Task")
        self.savetaskBtn.Bind(wx.EVT_BUTTON, self.SaveTask)
        # font button
        self.fontBtn = wx.Button(self.txtpanel, -1, "Font")
        self.fontBtn.Bind(wx.EVT_BUTTON, self.SelectFont)
        # colour button
        self.colourBtn = wx.Button(self.txtpanel, -1, "Colour")
        self.colourBtn.Bind(wx.EVT_BUTTON, self.SelectColour)
        # logoWidth slider
        self.logoWidthSldr = wx.Slider(self.txtpanel, -1, self.setting.logoWidth, 0, 100,
                                       size=(100,50),
                                       style=wx.SL_HORIZONTAL|wx.SL_LABELS, name='slider')
        # logoWidth spin
        self.logoWidthSpin = wx.SpinCtrl(self.txtpanel, -1, value=wx.EmptyString,
                                         size=(55,20), style=wx.SP_ARROW_KEYS,
                                         min=0, max=100, initial=self.setting.logoWidth,
                                         name="wxSpinCtrl")
        self.BindSliderAndSpinCtrl(self.logoWidthSldr, self.logoWidthSpin)
        
        # logoHeight slider
        self.logoHeightSldr = wx.Slider(self.txtpanel, -1, self.setting.logoHeight, 0, 100,
                                        size=(53,126),
                                        style=wx.SL_VERTICAL|wx.SL_LABELS, name='slider')
        # logoHeight spin
        self.logoHeightSpin = wx.SpinCtrl(self.txtpanel, -1, value=wx.EmptyString,
                                         size=(55,20), style=wx.SP_ARROW_KEYS,
                                         min=0, max=100, initial=self.setting.logoHeight,
                                         name="wxSpinCtrl")
        self.BindSliderAndSpinCtrl(self.logoHeightSldr, self.logoHeightSpin)
        self.synclogoWH = wx.CheckBox(self.txtpanel, -1, 'lock', pos=wx.DefaultPosition,
                                      size=wx.DefaultSize, style=0, name='checkBox')
        self.samelogoWH = wx.CheckBox(self.txtpanel, -1, 'same', pos=wx.DefaultPosition,
                                      size=wx.DefaultSize, style=0, name='checkBox')
        self.samelogoWH.SetValue(self.setting.samelogoWH)
        self.logoHeightSldr.Bind(wx.EVT_SCROLL, self.synclogoSldrHtoWFunc)
        self.logoWidthSldr.Bind(wx.EVT_SCROLL, self.synclogoSldrWtoHFunc)
        self.logoHeightSpin.Bind(wx.EVT_SPINCTRL, self.synclogoSpinHtoWFunc)
        self.logoWidthSpin.Bind(wx.EVT_SPINCTRL, self.synclogoSpinWtoHFunc)
        
        # logoX slider
        self.logoXSldr = wx.Slider(self.txtpanel, -1, self.setting.logoX, 0, 100,
                                   size=(100,50),
                                   style=wx.SL_HORIZONTAL|wx.SL_LABELS, name='slider')
        # logoX spin
        self.logoXSpin = wx.SpinCtrl(self.txtpanel, -1, value=wx.EmptyString,
                                         size=(55,20), style=wx.SP_ARROW_KEYS,
                                         min=0, max=100, initial=self.setting.logoX,
                                         name="wxSpinCtrl")
        self.BindSliderAndSpinCtrl(self.logoXSldr, self.logoXSpin)
        # logoY slider
        self.logoYSldr = wx.Slider(self.txtpanel, -1, self.setting.logoY, 0, 100,
                                   size=(53,126),
                                   style=wx.SL_VERTICAL|wx.SL_LABELS, name='slider')
        # logoY spin
        self.logoYSpin = wx.SpinCtrl(self.txtpanel, -1, value=wx.EmptyString,
                                         size=(55,20), style=wx.SP_ARROW_KEYS,
                                         min=0, max=100, initial=self.setting.logoY,
                                         name="wxSpinCtrl")
        self.BindSliderAndSpinCtrl(self.logoYSldr, self.logoYSpin)
        
        # txtX slider
        self.txtXSldr = wx.Slider(self.txtpanel, -1, self.setting.txtX, 0, 100,
                                  size=(100,50),
                                  style=wx.SL_HORIZONTAL|wx.SL_LABELS, name='slider')
        # txtX spin
        self.txtXSpin = wx.SpinCtrl(self.txtpanel, -1, value=wx.EmptyString,
                                         size=(55,20), style=wx.SP_ARROW_KEYS,
                                         min=0, max=100, initial=self.setting.txtX,
                                         name="wxSpinCtrl")
        self.BindSliderAndSpinCtrl(self.txtXSldr, self.txtXSpin)
        # txtY slider
        self.txtYSldr = wx.Slider(self.txtpanel, -1, self.setting.txtY, 0, 100,
                                  size=(53,126),
                                  style=wx.SL_VERTICAL|wx.SL_LABELS, name='slider')
        # txtY spin
        self.txtYSpin = wx.SpinCtrl(self.txtpanel, -1, value=wx.EmptyString,
                                         size=(55,20), style=wx.SP_ARROW_KEYS,
                                         min=0, max=100, initial=self.setting.txtY,
                                         name="wxSpinCtrl")
        self.BindSliderAndSpinCtrl(self.txtYSldr, self.txtYSpin)
        # opacity slider
        self.opacitySldr = wx.Slider(self.txtpanel, -1, self.setting.opacity, 0, 255,
                                     size=(53,126),
                                     style=wx.SL_VERTICAL|wx.SL_LABELS, name='slider')
        # opacity spin
        self.opacitySpin = wx.SpinCtrl(self.txtpanel, -1, value=wx.EmptyString,
                                         size=(55,20), style=wx.SP_ARROW_KEYS,
                                         min=0, max=255, initial=self.setting.opacity,
                                         name="wxSpinCtrl")
        self.BindSliderAndSpinCtrl(self.opacitySldr, self.opacitySpin)
        self.addwm = wx.CheckBox(self.txtpanel, -1, 'Add watermark', pos=wx.DefaultPosition,
                                 size=wx.DefaultSize, style=0, name='checkBox')
        self.addwm.SetValue(self.setting.addwm)
        self.logoFileBtn = wx.Button(self.txtpanel, -1, 'select logo file ...')
        self.logoFileBtn.Bind(wx.EVT_BUTTON, self.SelectLogoFile)
        self.chgfnbytxt = wx.CheckBox(self.txtpanel, -1, 'Change Filename by text', pos=wx.DefaultPosition,
                                      size=wx.DefaultSize, style=0, name='checkBox')
        self.chgfnbytxt.SetValue(self.setting.chgfnbytxt)
        self.saveSettingsBtn = wx.Button(self.txtpanel, -1, 'Save Settings')
        self.saveSettingsBtn.Bind(wx.EVT_BUTTON, self.SaveSettings)
        
        # is first
        self.firstLabel = wx.StaticText(self.txtpanel, -1, 'FirstImage',
                                        style=wx.ALIGN_LEFT)
        #self.firstLabel.Show(False)
        self.firstLabel.SetBackgroundColour('green')
        # is last
        self.lastLabel = wx.StaticText(self.txtpanel, -1, 'LastImage',
                                       style=wx.ALIGN_RIGHT)
        #self.lastLabel.Show(False)
        self.lastLabel.SetBackgroundColour('red')
        # current Image
        self.currentLabel = wx.StaticText(self.txtpanel, -1, '',
                                          style=wx.ALIGN_CENTER)
        self.currentLabel.SetBackgroundColour('white')
        # layout
        taskBox = wx.BoxSizer(wx.HORIZONTAL)
        taskBox.Add((20,20), 0, flag=wx.EXPAND)
        taskBox.Add(self.newtaskBtn, 1, flag=wx.EXPAND)
        taskBox.Add((20,20), 0, flag=wx.EXPAND)
        taskBox.Add(self.opentaskBtn, 1, flag=wx.EXPAND)
        taskBox.Add((20,20), 0, flag=wx.EXPAND)
        taskBox.Add(self.savetaskBtn, 1, flag=wx.EXPAND)
        taskBox.Add((20,20), 0, flag=wx.EXPAND)
        taskBox.Add(self.fontBtn, 1, flag=wx.EXPAND)
        taskBox.Add((20,20), 0, flag=wx.EXPAND)
        taskBox.Add(self.colourBtn, 1, flag=wx.EXPAND)
        taskBox.Add((20,20), 0, flag=wx.EXPAND)
        settingsBox = wx.StaticBoxSizer(wx.StaticBox(self.txtpanel, -1, "Settings"), wx.HORIZONTAL)
        v = wx.StaticBoxSizer(wx.StaticBox(self.txtpanel, -1, 'Logo Size'), wx.VERTICAL)
        h = wx.BoxSizer(wx.HORIZONTAL)
        h.Add(self.logoWidthSldr)
        h.Add(self.logoWidthSpin,flag=wx.RIGHT)
        v.Add(h)
        h = wx.BoxSizer(wx.HORIZONTAL)
        h.Add(self.logoHeightSldr, flag=wx.LEFT)
        h.Add(self.logoHeightSpin, flag=wx.LEFT)
        v.Add(h)
        h = wx.BoxSizer(wx.HORIZONTAL)
        h.Add(self.synclogoWH, flag=wx.LEFT)
        h.Add(self.samelogoWH, flag=wx.RIGHT)
        v.Add(h)
        settingsBox.Add(v)
        v = wx.StaticBoxSizer(wx.StaticBox(self.txtpanel, -1, 'Logo Position'), wx.VERTICAL)
        h = wx.BoxSizer(wx.HORIZONTAL)
        h.Add(self.logoXSldr)
        h.Add(self.logoXSpin,flag=wx.RIGHT)
        v.Add(h)
        h = wx.BoxSizer(wx.HORIZONTAL)
        h.Add(self.logoYSldr)
        h.Add(self.logoYSpin,flag=wx.RIGHT)
        v.Add(h)
        settingsBox.Add(v)
        v = wx.StaticBoxSizer(wx.StaticBox(self.txtpanel, -1, 'Text Position'), wx.VERTICAL)
        h = wx.BoxSizer(wx.HORIZONTAL)
        h.Add(self.txtXSldr)
        h.Add(self.txtXSpin,flag=wx.RIGHT)
        v.Add(h)
        h = wx.BoxSizer(wx.HORIZONTAL)
        h.Add(self.txtYSldr)
        h.Add(self.txtYSpin,flag=wx.RIGHT)
        v.Add(h)
        settingsBox.Add(v)
        v = wx.StaticBoxSizer(wx.StaticBox(self.txtpanel, -1, 'Opacity'), wx.VERTICAL)
        v.Add(self.opacitySldr)
        v.Add(self.opacitySpin, flag=wx.BOTTOM)
        settingsBox.Add(v)
        settingsBox.Add((10,10), 0, flag=wx.EXPAND)
        v = wx.BoxSizer(wx.VERTICAL)
        v.Add((10,10), 0, flag=wx.EXPAND)
        v.Add(self.addwm)
        v.Add((10,10), 0, flag=wx.EXPAND)
        v.Add(self.logoFileBtn)
        v.Add((10,10), 0, flag=wx.EXPAND)
        v.Add(self.chgfnbytxt)
        v.Add((10,10), 0, flag=wx.EXPAND)
        v.Add(self.saveSettingsBtn)
        settingsBox.Add(v)
        settingsBox.SetMinSize((750, 250))
        infoBox = wx.BoxSizer(wx.HORIZONTAL)
        infoBox.Add(self.firstLabel, 0, flag=wx.EXPAND|wx.LEFT)
        infoBox.Add((10,10), 0, flag=wx.EXPAND)
        infoBox.Add(self.currentLabel, 1, flag=wx.CENTER)
        infoBox.Add((10,10), 0, flag=wx.EXPAND)
        infoBox.Add(self.lastLabel, 0, flag=wx.EXPAND|wx.RIGHT)
        infoBox.SetMinSize((750, 50))
        buttonBox = wx.BoxSizer(wx.HORIZONTAL)
        buttonBox.Add((20,20), 0, flag=wx.EXPAND)
        buttonBox.Add(self.first, 1, flag=wx.EXPAND)
        buttonBox.Add((20,20), 0, flag=wx.EXPAND)
        buttonBox.Add(self.pre, 1, flag=wx.EXPAND)
        buttonBox.Add((20,20), 0, flag=wx.EXPAND)
        v = wx.BoxSizer(wx.VERTICAL)
        v.Add(self.done, 2, flag=wx.EXPAND)
        v.Add((5,5), 0, flag=wx.EXPAND)
        v.Add(self.discard, 1, flag=wx.EXPAND)
        buttonBox.Add(v, 1, flag=wx.EXPAND)
        buttonBox.Add((20,20), 0, flag=wx.EXPAND)
        buttonBox.Add(self.next, 1, flag=wx.EXPAND)
        buttonBox.Add((20,20), 0, flag=wx.EXPAND)
        buttonBox.Add(self.last, 1, flag=wx.EXPAND)
        buttonBox.Add((20,20), 0, flag=wx.EXPAND)
        txtpanelsizer = wx.BoxSizer(wx.VERTICAL)
        txtpanelsizer.Add((-1,10), 0, flag=wx.EXPAND)
        txtpanelsizer.Add(taskBox, 1, flag=wx.EXPAND)
        txtpanelsizer.Add(settingsBox, 0, flag=wx.EXPAND)
        txtpanelsizer.Add((-1,10), 0, flag=wx.EXPAND)
        txtpanelsizer.Add(infoBox, 1, flag=wx.EXPAND)
        txtpanelsizer.Add((-1,10), 0, flag=wx.EXPAND)
        txtpanelsizer.Add(self.txt, 10, flag=wx.EXPAND)
        txtpanelsizer.Add((-1,10), 0, flag=wx.EXPAND)
        txtpanelsizer.Add(buttonBox, 1, flag=wx.EXPAND)
        txtpanelsizer.Add((-1,10), 0, flag=wx.EXPAND)
        self.txtpanel.SetSizer(txtpanelsizer)
        self.txtpanel.Fit()
        self.txtpanel.SetAutoLayout(1)
    
    def AddMenus(self):
        menubar = wx.MenuBar()
        menu = wx.Menu()
        new = menu.Append(-1, 'New Task', 'Create a new task')
        self.Bind(wx.EVT_MENU, self.NewTask, new)
        open = menu.Append(-1, 'Open Task (Ctrl-o)', 'Open a task')
        self.Bind(wx.EVT_MENU, self.OpenTask, open)
        save = menu.Append(-1, 'Save Task (Ctrl-s)', 'Save current task')
        self.Bind(wx.EVT_MENU, self.SaveTask, save)
        menubar.Append(menu, 'File')
        menu = wx.Menu()
        first = menu.Append(-1, 'First (Ctrl-a)', 'Goto First Image')
        self.Bind(wx.EVT_MENU, self.GoFirst, first)
        pre = menu.Append(-1, 'Pre (Ctrl-p)', 'Goto Previous Image')
        self.Bind(wx.EVT_MENU, self.GoPre, pre)
        next = menu.Append(-1, 'Next (Ctrl-n)', 'Goto Next Image')
        self.Bind(wx.EVT_MENU, self.GoNext, next)
        last = menu.Append(-1, 'Last (Ctrl-e)', 'Goto Last Image')
        self.Bind(wx.EVT_MENU, self.GoLast, last)
        preview = menu.Append(-1, 'Preview (Ctrl-i)', 'Preview')
        self.Bind(wx.EVT_MENU, self.Preview, preview)
        discard = menu.Append(-1, 'Discard (Ctrl-k)', 'Discard')
        self.Bind(wx.EVT_MENU, self.Discard, discard)
        menubar.Append(menu, 'Edit')
        menu = wx.Menu()
        about = menu.Append(-1, 'About', 'About')
        self.Bind(wx.EVT_MENU, self.About, about)
        menubar.Append(menu, 'Help')
        self.SetMenuBar(menubar)
        
        acceltbl = wx.AcceleratorTable( [ #Using an accelerator table
            (wx.ACCEL_CTRL, ord('o'), open.GetId()),
            (wx.ACCEL_CTRL, ord('s'), save.GetId()),
            (wx.ACCEL_CTRL, ord('a'), first.GetId()),
            (wx.ACCEL_CTRL, ord('p'), pre.GetId()),
            (wx.ACCEL_CTRL, ord('n'), next.GetId()),
            (wx.ACCEL_CTRL, ord('e'), last.GetId()),
            (wx.ACCEL_CTRL, ord('i'), preview.GetId()),
            (wx.ACCEL_CTRL, ord('k'), discard.GetId()),
            ])
        self.SetAcceleratorTable(acceltbl)
    
    def BindSliderAndSpinCtrl(self, slider, spinctrl):
        slider.Bind(wx.EVT_SCROLL, lambda e: spinctrl.SetValue(slider.GetValue()))
        slider.Bind(wx.EVT_SCROLL_ENDSCROLL, lambda e: spinctrl.SetValue(slider.GetValue()))
        spinctrl.Bind(wx.EVT_SPINCTRL, lambda e: slider.SetValue(spinctrl.GetValue()))
    
    def synclogoSldrHtoWFunc(self, e):
        v = self.logoHeightSldr.GetValue()
        self.logoHeightSpin.SetValue(v)
        if self.synclogoWH.IsChecked():
            self.logoWidthSldr.SetValue(v)
            self.logoWidthSpin.SetValue(v)
    
    def synclogoSldrWtoHFunc(self, e):
        v = self.logoWidthSldr.GetValue()
        self.logoWidthSpin.SetValue(v)
        if self.synclogoWH.IsChecked():
            self.logoHeightSldr.SetValue(v)
            self.logoHeightSpin.SetValue(v)
    
    def synclogoSpinHtoWFunc(self, e):
        v = self.logoHeightSpin.GetValue()
        self.logoHeightSldr.SetValue(v)
        if self.synclogoWH.IsChecked():
            self.logoWidthSldr.SetValue(v)
            self.logoWidthSpin.SetValue(v)
    
    def synclogoSpinWtoHFunc(self, e):
        v = self.logoWidthSpin.GetValue()
        self.logoWidthSldr.SetValue(v)
        if self.synclogoWH.IsChecked():
            self.logoHeightSldr.SetValue(v)
            self.logoHeightSpin.SetValue(v)
    
    def LoadImageFile(self, item):
        # water mark
        if item.txt:
            self.txt.SetValue(item.txt)
        else:
            self.txt.SetValue('')
        if item.dstfn and os.path.exists(item.dstfn):
            self.image = wx.Image(item.dstfn)
        else:
            item.dstfn = None
            self.image = wx.Image(item.srcfn)
        
        self.LoadImage()
    
    def LoadImage(self):
        dcx, dcy = self.imgpanel.GetClientSize()
        dc = wx.BufferedDC(wx.ClientDC(self.imgpanel), self.imageBuffer)
        brush = dc.GetBackground()
        colour = brush.GetColour()
        colour.Set(192,192,192,255)
        brush.SetColour(colour)
        dc.SetBackground(brush)
        dc.Clear()
        if self.image and dcx>50 and dcy>50:
            x, y = self.image.GetSize()
            ratio = min(dcx*1.0/x, dcy*1.0/y)
            image = self.image.Scale(x*ratio, y*ratio)
            bmp = wx.BitmapFromImage(image)
            dc.DrawBitmap(bmp, (dcx-x*ratio)/2, (dcy-y*ratio)/2)
    
    def SetSrcDir(self, srcpath):
        if not os.path.exists(self.renamepath):
            os.mkdir(self.renamepath)
        if not os.path.exists(self.dstpath):
            os.mkdir(self.dstpath)
        self.FileList = []
        for fn in os.listdir(self.srcpath):
            if App.IsSupportedFile(fn):
                item = Dick()
                item.srcfn = os.path.join(self.srcpath, fn)
                item.txt = None
                item.renamefn = None
                item.dstfn = None
                self.FileList.append(item)
        self.GoTo(0)
    
    def GoFirst(self, event):
        self.SaveImage()
        self.GoTo(0)
    
    def GoPre(self, event):
        self.SaveImage()
        self.GoTo(self.Index-1)
    
    def GoTo(self, index):
        if len(self.FileList)==0:
            return
        listLen = len(self.FileList)
        if index <= 0:
            index = 0
        elif index >= listLen-1:
            index = listLen -1
        if index == 0:
            self.firstLabel.Show(True)
        else:
            self.firstLabel.Show(False)
        if index == listLen-1:
            self.lastLabel.Show(True)
        else:
            self.lastLabel.Show(False)
        self.Index = index
        self.LoadImageFile(self.FileList[index])
        self.currentLabel.SetLabel(os.path.basename(self.FileList[index].srcfn))
        self.previewed = False
        self.txt.SelectAll()
        self.txt.SetFocus()
    
    def Preview(self, event):
        if len(self.FileList)==0:
            return
        txt = self.txt.GetValue()
        memDC = wx.MemoryDC()
        bmp = wx.Bitmap(self.FileList[self.Index].srcfn)
        x, y = bmp.GetSize()
        memDC.SelectObject(bmp)
        if self.setting.addwm:
            if not os.path.exists(self.setting.logoFile):
                self.SelectLogoFile(None)
            if os.path.exists(self.setting.logoFile):
                self.LoadLogoFile(x*self.setting.logoWidth/100, y*self.setting.logoHeight/100)
                memDC.DrawBitmap(self.logoBitmap, x*self.setting.logoX/100, y*self.setting.logoY/100, True)
        
        if txt.strip():
            x = x*self.setting.txtX/100
            y = y*self.setting.txtY/100
            memDC.SetFont(self.font)
            memDC.SetTextForeground(self.colour)
            lw, lh = memDC.GetTextExtent('I')
            for line in txt.split('\n'):
                memDC.DrawText(line, x, y)
                y = y + lh
        memDC.SelectObject(wx.NullBitmap)
        self.image = bmp.ConvertToImage()
        self.LoadImage()
        self.previewed = True
    
    def Discard(self, event):
        if len(self.FileList)==0:
            return
        self.txt.SetValue('')
        item = self.FileList[self.Index]
        if item.dstfn and os.path.exists(item.dstfn):
            os.remove(item.dstfn)
        item.txt = None
        item.dstfn = None
        self.image = wx.Image(item.srcfn)
        self.LoadImage()
        self.previewed = False
    
    def GoNext(self, event):
        self.SaveImage()
        self.GoTo(self.Index+1)
    
    def GoLast(self, event):
        self.SaveImage()
        self.GoTo(len(self.FileList)-1)
    
    def SaveImage(self):
        if self.previewed:
            item = self.FileList[self.Index]
            txt = self.txt.GetValue()
            refinedfn = self.refineFilename(os.path.basename(item.srcfn), txt)
            dstfn = os.path.join(self.dstpath, refinedfn)
            renamefn = os.path.join(self.renamepath, refinedfn)
            replace = False
            for index, i in enumerate(self.FileList):
                if (i.dstfn == dstfn or i.renamefn == renamefn) and index != self.Index:
                    if not replace:
                        if wx.MessageBox('Same text used, replace?', caption='Duplicate text', style=wx.YES|wx.NO) == wx.NO:
                            return
                        else:
                            replace = True
                    i.txt = None
                    os.remove(dstfn)
                    i.dstfn = None
                    os.remove(renamefn)
                    i.renamefn = None
            
            # save current
            item.txt = txt
            if item.renamefn and os.path.exists(item.renamefn):
                os.remove(item.renamefn)
            item.renamefn = renamefn
            shutil.copy(item.srcfn, item.renamefn)
            if item.dstfn and os.path.exists(item.dstfn):
                os.remove(item.dstfn)
            item.dstfn = dstfn
            self.image.SaveFile(item.dstfn, wx.BITMAP_TYPE_JPEG)
            self.dirty = True
    
    def refineFilename(self, srcfn, txt):
        if not self.setting.chgfnbytxt:
            return srcfn
        base, ext = os.path.splitext(srcfn)
        mapping = {'\\': '',
                    '/': '',
                    ':': '',
                    '*': '',
                    '?': '',
                    '"': '',
                    '<': '',
                    '>': '',
                    '|': '',
                    '\n': '',
                    }
        for k in mapping:
            txt = txt.replace(k,mapping[k])
        if txt:
            return txt + ext
        else:
            return srcfn
    
    def NewTask(self, event):
        dialog = wx.DirDialog(None, "Choose Source Directory:", style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
        if dialog.ShowModal() == wx.ID_OK:
            srcpath = dialog.GetPath()
            srcpath = srcpath.rstrip(os.path.sep)
        else:
            dialog.Destroy()
            return
        dialog.SetMessage("Choose Renamed Directory:")
        if dialog.ShowModal() == wx.ID_OK:
            renamepath = dialog.GetPath()
            renamepath = renamepath.rstrip(os.path.sep)
        else:
            dialog.Destroy()
            return
        dialog.SetMessage("Choose Destination Directory:")
        if dialog.ShowModal() == wx.ID_OK:
            dstpath = dialog.GetPath()
            dstpath = dstpath.rstrip(os.path.sep)
        else:
            dialog.Destroy()
            return
        dialog.Destroy()
        if len(set([srcpath, renamepath, dstpath])) != 3:
            wx.MessageBox('The 3 directories can not be the same.\nPlease try again.', caption='', style=wx.OK)
            return
        self.srcpath = srcpath
        self.renamepath = renamepath
        self.dstpath = dstpath
        self.taskfn = None
        self.SetSrcDir(srcpath)
    
    def OpenTask(self, event):
        wildcard = 'ImageText Task (*.itt)|*.itt|' \
                   'All files (*.*)|*.*'
        dialog = wx.FileDialog(None, 'Choose a file', os.path.expanduser('~'),
                               '', wildcard, wx.OPEN)
        if dialog.ShowModal() == wx.ID_OK:
            taskfn = dialog.GetPath()
            self.LoadTaskFromFile(taskfn)
        
        dialog.Destroy()
    
    def SaveTask(self, event):
        if not self.taskfn:
            wildcard = 'ImageText Task (*.itt)|*.itt|' \
                       'All files (*.*)|*.*'
            dialog = wx.FileDialog(None, 'Choose a file', os.path.expanduser('~'),
                                   '', wildcard, wx.SAVE|wx.OVERWRITE_PROMPT)
            if dialog.ShowModal() == wx.ID_OK:
                fn = dialog.GetPath()
                self.taskfn = fn
            else:
                dialog.Destroy()
                return
        self.SaveTaskToFile(self.taskfn)
    
    def LoadTaskFromFile(self, fn):
        self.FileList = []
        f = open(fn)
        try:
            obj = json.load(f)
        except:
            wx.MessageBox('Invalid file.' % version, caption='', style=wx.OK)
            return
        finally:
            f.close()
        if len(obj) == 3:
            # without version
            version = 0
            self.srcpath, self.dstpath, flist = obj
            dialog = wx.DirDialog(None, "The file is created by a older version of program.\nYou need to choose Renamed Directory to upgrade this file to current version:", style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
            if dialog.ShowModal() == wx.ID_OK:
                renamepath = dialog.GetPath()
                renamepath = renamepath.rstrip(os.path.sep)
            else:
                dialog.Destroy()
                return
            self.renamepath = renamepath
            for i in flist:
                item = Dick()
                item.srcfn = os.path.join(self.srcpath, i['srcfn'])
                item.txt = i['txt']
                item.dstfn = i['dstfn']
                if item.dstfn:
                    item.renamefn = os.path.join(renamepath, os.path.basename(item.dstfn))
                    shutil.copy(item.srcfn, item.renamefn)
                else:
                    item.renamefn = None
                self.FileList.append(item)
        else:
            version = obj[0]
            version, self.srcpath, self.renamepath, self.dstpath, flist = obj
            if version > self.version:
                wx.MessageBox('The file is created by a newer version of program.\nPlease use a newer program supporting file version:[%d].' % version, caption='', style=wx.OK)
                return
            for i in flist:
                item = Dick()
                item.srcfn = os.path.join(self.srcpath, i['srcfn'])
                item.txt = i['txt']
                item.renamefn = i['renamefn']
                item.dstfn = i['dstfn']
                self.FileList.append(item)
        self.taskfn = fn
        self.GoTo(0)
    
    def SaveTaskToFile(self, fn):
        f = open(fn, 'wb')
        f.write(json.dumps([self.version, self.srcpath, self.renamepath, self.dstpath, self.FileList]))
        f.close()
        self.dirty = False
    
    def SelectFont(self, event):
        fd = wx.FontData()
        fd.SetInitialFont(self.font)
        dialog = wx.FontDialog(None, fd)
        if dialog.ShowModal() == wx.ID_OK:
            data = dialog.GetFontData()
            font = data.GetChosenFont()
            self.font = font
            self.setting.pointSize = self.font.GetPointSize()
            self.setting.family = self.font.GetFamily()
            self.setting.style = self.font.GetStyle()
            self.setting.weight = self.font.GetWeight()
            self.setting.underline = self.font.GetUnderlined()
            self.setting.faceName = self.font.GetFaceName()
            self.setting.save()
        dialog.Destroy()
     
    def SelectColour(self, event):
        dialog = self.colourDlg
        dialog.GetColourData().SetChooseFull(True)
        dialog.GetColourData().SetColour(self.colour)
        if dialog.ShowModal() == wx.ID_OK:
            data = dialog.GetColourData()
            self.colour = data.GetColour()
            self.setting.red = self.colour.Red()
            self.setting.green = self.colour.Green()
            self.setting.blue = self.colour.Blue()
            self.setting.save()
        ## dialog.Destroy()
    
    def SelectLogoFile(self, event):
        dialog = wx.FileDialog(None, 'Choose a image file', os.path.expanduser('~'),
                               '', 'All files (*.*)|*.*', wx.OPEN)
        if dialog.ShowModal() == wx.ID_OK:
            self.setting.logoFile = dialog.GetPath()
        
        dialog.Destroy()
    
    def SaveSettings(self, event):
        self.setting.logoWidth = self.logoWidthSldr.GetValue()
        self.setting.logoHeight = self.logoHeightSldr.GetValue()
        self.setting.samelogoWH = self.samelogoWH.IsChecked()
        self.setting.logoX = self.logoXSldr.GetValue()
        self.setting.logoY = self.logoYSldr.GetValue()
        self.setting.txtX = self.txtXSldr.GetValue()
        self.setting.txtY = self.txtYSldr.GetValue()
        self.setting.opacity = self.opacitySldr.GetValue()
        self.setting.addwm = self.addwm.IsChecked()
        self.setting.chgfnbytxt = self.chgfnbytxt.IsChecked()
        self.setting.save()
    
    def LoadLogoFile(self, w, h):
        logo = wx.Image(self.setting.logoFile)
        # resize
        if self.setting.samelogoWH:
            h = w
        logo.Rescale(w, h, wx.IMAGE_QUALITY_NORMAL)
        if not logo.HasAlpha():
            logo.InitAlpha()
            avalue = struct.pack('B', self.setting.opacity)
            logo.SetAlphaData(avalue*w*h)
        else:
            ab = logo.GetAlphaBuffer()
            ratio = self.setting.opacity*1.0/255
            a = array.array('B')
            a.fromstring(ab)
            for i in xrange(len(a)):
                a[i] = int(a[i]*ratio)
            logo.SetAlphaData(a)
        
        self.logoBitmap = wx.BitmapFromImage(logo)

    def About(self, event):
        wx.MessageBox('Author: sevenever@gmail.com\nVersion: %s'%open(self.rm.get('Version')).read(), caption='About', style=wx.OK)
    

class App(wx.App):  #5 wx.App子类
    """Application class."""
    def __init__(self, resourcemanager, redirect=False, filename=None):
        self.rm = resourcemanager
        wx.App.__init__(self, redirect, filename)
    def OnInit(self):
        if not self.Authenticate():
            return False
        #6 图像处理
        settings = Settings('settings.json')
        self.frame = MainFrame(settings=settings, resourcemanager=self.rm)
        
        self.SetTopWindow(self.frame)
        return True
    def SetTaskFile(self, fn):
        self.frame.LoadTaskFromFile(fn)
    
    @staticmethod 
    def IsSupportedFile(filename):
        mime = mimetypes.guess_type(filename)[0]
        if not mime:
            return False
        return mime.split("/")[0].lower() == "image"
    
    def Authenticate(self):
        with wx.TextEntryDialog(None, "Input the key you got from administrator:", 'Key', '') as dlg:
            if dlg.ShowModal() == wx.ID_OK and self.auth_to_server(dlg.GetValue()):
                return True
            else:
                return False
    
    def auth_to_server(self, key):
        import urllib2
        try:
            result = urllib2.urlopen(AUTH_URL % key).read()
            if result == 'TRUE':
                return True
            else:
                print 'result is', result
                wx.MessageBox('Invalid Key:\n%s' % key, caption='Fail', style=wx.OK)
                return False
        except Exception,e:
            wx.MessageBox('Failed to Authenticate:\n%s' % e.message, caption='Fail', style=wx.OK)
            traceback.print_exc()
            return False

def main():  #7
    # resource manager must be created first
    rm = ResourceManager()
    wkdir = os.path.expanduser('~/.' + APP_NAME)
    if not os.path.exists(wkdir):
        os.mkdir(wkdir)
    os.chdir(wkdir)
    if not os.path.exists('debug'):
        app = App(rm, redirect=True, filename='imagetext.log')
    else:
        app = App(rm, redirect=False)
    if len(sys.argv) > 1:
        app.SetTaskFile(sys.argv[1])
    app.MainLoop()

if __name__ == '__main__':
     main()
